package com.inetools.gwetons.client;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.SortedMap;
import java.util.TreeMap;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.Command;

public class ResourceDeliverDictionary {
	protected static ResourceDeliverDictionary _instance = new ResourceDeliverDictionary(); 
	private ResourceDeliverDictionary() {		
	}

	// Sorted by order when it's added
	// This doesn't need to be Map (pair of data). This just needs to be sorted key by time when it's added.
	// For this, let's use LinkedHashMap which stores key and value in the order when it's added.
	// So, this can provide the function of query value by key too.
	// Sorted by the number of access 
	// 	This can be done by SortedMap (TreeMap)
	// 	No, I cannot use TreeMap because Map doesn't accept duplicate key
	// Sorted by key
	// 	This doesn't need to be Map (pair of data). This just needs to be sorted key by alphabetically.
	//	For this, let's use SortedMap (TreeMap) which store key and the number of access. 
	
	// Store key and value (Element)
	protected static HashMap<String, Element> orderPreservationHashMap = 
		new HashMap<String, Element>();
	// Store access count as key and key string LinkedHashSet
	protected static SortedMap<Integer, LinkedHashSet<String>> accessCounterSortedMap = 
		new TreeMap<Integer, LinkedHashSet<String>>();
	// Store key string as key and access count
	protected static SortedMap<String, Integer> keySortedMap = new TreeMap<String, Integer>();
	
	protected static int dictionaryMaxSize = 200;
	public static int getDictionaryMaxSize() {
		return dictionaryMaxSize;
	}
	public static void setDictionaryMaxSize( int maxSize) {
		dictionaryMaxSize = maxSize;
	}
	protected static int countToReduce = 50;
	public static int getCountToReduce() {
		return countToReduce;
	}
	public static void setCountToReduce(int countToReduce) {
		ResourceDeliverDictionary.countToReduce = countToReduce;
	}
	protected class ReduceDictionarySize implements Command {
		protected String keyStr;
		public ReduceDictionarySize( String keyStr) {
			this.keyStr = keyStr;
		}
		
		@Override
		public void execute() {
			int dictionaryNewSize = dictionaryMaxSize - countToReduce;
			
			Integer leastAccessCount = null;
			while( keySortedMap.size() > dictionaryNewSize) {
				// Remove from less access count and older entry
				if ( leastAccessCount == null) {
					leastAccessCount = accessCounterSortedMap.firstKey();
				}
				else {
					SortedMap<Integer, LinkedHashSet<String>> tailMap = 
						accessCounterSortedMap.tailMap( Integer.valueOf( leastAccessCount + 1));
					leastAccessCount = tailMap.firstKey();
				}
System.out.println( "leastAccessCount = " + leastAccessCount);
				LinkedHashSet<String> lessAccessKeys = accessCounterSortedMap.get( leastAccessCount);
System.out.println( "lessAccessKeys = " + lessAccessKeys);
				Iterator<String> lessAccessKeyIterator = lessAccessKeys.iterator();
				while( lessAccessKeyIterator.hasNext()) {
					String keyToRemove = lessAccessKeyIterator.next();
					if ( keyToRemove.equalsIgnoreCase( keyStr)) continue; // while
					keySortedMap.remove( keyToRemove);
					orderPreservationHashMap.remove( keyToRemove);
					lessAccessKeyIterator.remove();
					
					if ( keySortedMap.size() == dictionaryNewSize) break; // while
				} // while
				if ( lessAccessKeys.size() < 1) {
					accessCounterSortedMap.remove( leastAccessCount);
				}
			} // while
		}
	} // protected class ReduceDictionarySize implements Command
	
	/**
	 * This does not overwrite value for key when key already exists in dictionary.
	 * To overwrite value for key, need to remove entry first, and add entry.
	 * @param key
	 * @param innerHTMLStr
	 * @throws UnsupportedOperationException
	 * @throws ClassCastException
	 * @throws NullPointerException
	 * @throws IllegalArgumentException
	 */
	public static void add( String key, Element resourceElement) 
	throws UnsupportedOperationException, ClassCastException, NullPointerException, IllegalArgumentException 
	{
		if ( ( key == null) || "".equals( key)) 
			throw new IllegalArgumentException( "key argument must be substantial string, niether null nor empty string.");
		if ( resourceElement == null) 
			throw new IllegalArgumentException( "innerHTMLStr argument must be substantial string, niether null nor empty string.");		

		if ( keySortedMap.containsKey( key)) return;
//System.out.println( "keySortedMap.size() = " + keySortedMap.size());
		if ( keySortedMap.size() >= dictionaryMaxSize) {
			// Reduce size of dictionary
//System.out.println( "key = " + key);
//			DeferredCommand.addCommand( _instance.new ReduceDictionarySize( key));
			Scheduler.get().scheduleDeferred( _instance.new ReduceDictionarySize( key));
		}
		
		Integer accessCount = new Integer( 1);
		keySortedMap.put( key, accessCount);
		LinkedHashSet<String> linkedHashSet = accessCounterSortedMap.get( accessCount);
		if ( linkedHashSet == null) {
			linkedHashSet = new LinkedHashSet<String>();
		}
		linkedHashSet.add( key);
		accessCounterSortedMap.put( accessCount, linkedHashSet);
		orderPreservationHashMap.put( key, resourceElement);
	} // public static void add( String key, String value)
	
	public static void remove( String key) throws UnsupportedOperationException, ClassCastException, NullPointerException {
		if ( !keySortedMap.containsKey( key)) return;
		Integer accessCount = keySortedMap.get( key);
		accessCounterSortedMap.get( accessCount).remove( key);
		LinkedHashSet<String> linkedHashSet = accessCounterSortedMap.get( accessCount);
		if ( linkedHashSet.size() < 1) {
			accessCounterSortedMap.remove( accessCount);
		}
		keySortedMap.remove( key);
		orderPreservationHashMap.remove( key);
	}
	
	public static Element getValue( String key) throws ClassCastException, NullPointerException {
		if ( !keySortedMap.containsKey( key)) return null;
		
		Integer accessCount = keySortedMap.get( key);
		accessCounterSortedMap.get( accessCount).remove( key);
		accessCount = Integer.valueOf( accessCount.intValue() + 1);
		LinkedHashSet<String> linkedHashSet = accessCounterSortedMap.get( accessCount);
		if ( linkedHashSet == null) {
			accessCounterSortedMap.put( accessCount, new LinkedHashSet<String>());
		}
		accessCounterSortedMap.get( accessCount).add( key);
		keySortedMap.put( key, accessCount);
		
		return orderPreservationHashMap.get( key);
	}
}
